import java.awt.Frame;
import java.awt.Label;
import java.awt.List;
import java.awt.TextArea;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;

class Code{
	final static byte ok = 0;
	final static byte newUser = 1;
	final static byte msg = 2;
	final static byte roomIsFull = 3;
	final static byte idChkErr = 4;
	final static byte userExit = 5;
}

public class Server {

	TextArea ta_all = null;
	List l_user = null;

	// user info
	class UserInfo {
		int id;
		String username;
		Socket sk;
		String ip;

		UserInfo(int i, String u, Socket s, String str) {
			this.id = i;
			this.username = u;
			this.sk = s;
			this.ip = str;
		}
	}
	Socket skt = null;
	ArrayList<UserInfo> arr_user = null;

	void launchFrame() {
		Frame f_server = new Frame();
		f_server.setBounds(300, 150, 700, 600);
		f_server.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				System.exit(0);
			}
		});
		ta_all = new TextArea();
		ta_all.setBounds(10, 24, 485, 573);

		l_user = new List();
		l_user.setBounds(495, 27, 203, 573);

		f_server.add(ta_all);
		f_server.add(l_user);
		f_server.add(new Label());
		f_server.setVisible(true);
		this.serverStart();
	}

	void serverStart() {
		try {
			boolean flg = true;
			ServerSocket ss = new ServerSocket(9999);
			DataOutputStream dos_chkID = null;
			DataInputStream dis_chkID = null;
			arr_user = new ArrayList<UserInfo>();
			UserInfo user_info = null;

			while (flg) {

				skt = ss.accept(); // wait for user to connect
				dos_chkID = new DataOutputStream(skt.getOutputStream());
				dis_chkID = new DataInputStream(skt.getInputStream());

				//max number check
				if (arr_user.size() < 20) {
					
					dos_chkID.writeInt(Code.ok);	//tell client : this room is available for you
					
					//user id check
					String u_name = dis_chkID.readUTF();
					if(chkUserID(u_name)){
						dos_chkID.writeInt(Code.idChkErr);	//same user already exists, user id check failed
						continue;
					}else{
						dos_chkID.writeInt(Code.ok);	//user id check OK
					}
					
					String ip_s = skt.getInetAddress().toString();
					p("IP : " + ip_s + " Port : " + skt.getLocalPort() + "  Connected.");
					
					//user register
					user_info = new UserInfo(arr_user.size() + 1, u_name, skt, ip_s);
					arr_user.add(user_info);
					l_user.add(u_name);
					sendToAll(Code.newUser, u_name);	//send to client's user list area

					new Thread(new Waiter(user_info)).start();

				} else {
					dos_chkID.writeInt(Code.roomIsFull);	//refuse to connect, and start to wait for the next
					continue;
				}
			}
			ss.close();

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	boolean chkUserID(String uid){
		boolean rtn = false;
		Iterator<UserInfo> it = arr_user.iterator();
		while(it.hasNext()){
			UserInfo ui = it.next();
			if(ui.username.equals(uid)){
				rtn = true;
				break;
			}
		}
		return rtn;
	}
	
	void p(String s) {
		ta_all.setText(ta_all.getText() + '\n' + s);
	}

	class Waiter implements Runnable {

		UserInfo userinfo = null;
		
		Waiter(UserInfo u_info){
			this.userinfo = u_info;
		}
		
		public void run() {
			pt("new thread: " + Thread.currentThread().getName());

			try {
				DataInputStream dis = new DataInputStream(skt.getInputStream());
				DataOutputStream dos = new DataOutputStream(skt.getOutputStream());

				//update user list
				Iterator<UserInfo> l_it = arr_user.iterator();
				while(l_it.hasNext()){
					UserInfo ul_info = l_it.next();
					if(!this.userinfo.username.equals(ul_info.username)){
						dos.writeUTF(1 + ul_info.username);
					}
				}
				
				//send ons's msg to everyone
				String client_msg = null;
				boolean t_flag = true;
				while (t_flag) {
					client_msg = dis.readUTF();
					if(client_msg.length() < 1){
						p("ERROR! client msg is null");
						break;
					}

					byte code = Byte.parseByte(client_msg.substring(0, 1));
					String usrMsg = null;	//maybe useful
					
					if(client_msg.length() > 1){
						usrMsg = client_msg.substring(1);
					}
					
					switch(code){
						case Code.userExit:
							l_user.remove(userinfo.username);
							arr_user.remove(userinfo);
							p("[" + userinfo.username + "] : logout.");
							sendToAll(Code.userExit, userinfo.username);
							t_flag = false;
							break;
						case Code.msg:
							p(userinfo.username + " : " + usrMsg);
							sendToAll(Code.msg, userinfo.username + " : " + usrMsg);	//send to client's ta_all area
							break;
					}
					
				}

			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}
	
	void sendToAll(byte code, String str) throws IOException {
		Iterator<UserInfo> it = arr_user.iterator();
		UserInfo u_info = null;
		DataOutputStream dos = null;
		while (it.hasNext()) {
			u_info = it.next();
			dos = new DataOutputStream(u_info.sk.getOutputStream());
			dos.writeUTF(code + str);
		}
	}
	
	public static void main(String[] args) {
		new Server().launchFrame();
	}

	public void pt(Object o) {
		System.out.println(o);
	}
}
